//  Copyright (c) 2008  Yoong-ki Ahn & Youngjin Kim < mexif@googlegroups.com>
//  See the enclosed file COPYING for license information (MIT Licence).
//  If you did not receive this file,
//  see http://www.opensource.org/licenses/mit-license.php.

#define _MEXIFLIB_INSIDE
#include "mexiflib.h"
#include <memory.h>


mexif_return_t MEXIF_API mexif_jpeg_init( mexif_jpeg_t* jpeg, const void* data, unsigned int len )
{
	const unsigned char* pb;

	memset( jpeg, 0, sizeof(mexif_jpeg_t) );

	//	check chunk marker
	if ( len <= 4 )
		return MEXIF_RET_CORRUPT_DATA;

	pb = (const unsigned char*) data;
	if ( !( pb[0] == 0xff && pb[1] == MEXIF_JPEG_MARKER_StartOfImage ))
		return MEXIF_RET_CORRUPT_DATA;

	jpeg->data   = pb;
	jpeg->length = len;

	jpeg->header_position = pb;
	jpeg->chunk_type      = pb[1];
	jpeg->chunk_length    = 0;
	jpeg->next_position   = pb + 2;
	return MEXIF_RET_OK;
}

void MEXIF_API mexif_jpeg_close( mexif_jpeg_t* pjpeg )
{
	memset( pjpeg, 0, sizeof(mexif_jpeg_t) );
}

void MEXIF_API mexif_jpeg_next( mexif_jpeg_t* pjpeg )
{
	size_t pos; 
	const unsigned char* pb;
	if ( pjpeg->next_position == 0 )
		return;	//	ok. done without error.

	// out of range
	pos = pjpeg->next_position - pjpeg->data;
	if ( pos > pjpeg->length )
		return;	//	data truncated.

	pb = pjpeg->next_position;
	if ( pjpeg->chunk_type == MEXIF_JPEG_MARKER_EndOfImage )
	{
		// make it out of range.
		//pjpeg->next_position = pjpeg->data + pjpeg->length + 1;
		pjpeg->next_position = 0;
		return;	//	ok. done.
	}

	if ( pjpeg->in_image )
	{
		int marker = 0;
		//	we have to scan.
		pjpeg->header_position = pb;
		pjpeg->chunk_type      = MEXIF_JPEG_MARKER_PseudoImage;
		pjpeg->chunk_length    = 0;

		//	skip compressed image data.
		//	0xff,  0x00 is not marker. keep skipping.
		//	0xff, !0x00 is marker. stop here.
		for ( ; ; ++pb )
		{
			if ( marker )
			{
				if ( *pb != 0 )
				{
					--pb;
					break;
				}
				else
					marker = 0;
			}
			if ( *pb == 0xff )
				marker = 1;
		}
		pjpeg->chunk_length  = pb - pjpeg->header_position;
		pjpeg->next_position = pb;

		if ( marker )
			pjpeg->in_image = 0;
	}
	else
	{
		if ( pb[0] != 0xff )
			return;		//	error. data corrupt.

		pjpeg->header_position = pb;
		pjpeg->chunk_type    = pb[1];

		if ( MEXIF_JPEG_MARKER_RST0 <= pjpeg->chunk_type && pjpeg->chunk_type <= MEXIF_JPEG_MARKER_RST7 )
		{
			pjpeg->in_image = 1;
			pjpeg->chunk_length = 0;
		}
		else if ( pjpeg->chunk_type != MEXIF_JPEG_MARKER_EndOfImage )
		{
			pjpeg->chunk_length  = ( pb[2] << 8 ) | pb[3];
		}
		else
		{
			pjpeg->chunk_length = 0;
		}

		if ( pjpeg->chunk_type == MEXIF_JPEG_MARKER_StartOfScan )
			pjpeg->in_image = 1;

		pjpeg->next_position = pb + pjpeg->chunk_length + 2;
	}

	//	ok. no error.
}

mexif_bool MEXIF_API mexif_jpeg_isvalid( mexif_jpeg_t* pjpeg )
{
	size_t pos;
	if ( pjpeg->data == 0 )
		return MEXIF_FALSE;

	// out of range
	pos = pjpeg->next_position - pjpeg->data;
	if ( pos > pjpeg->length )
		return MEXIF_FALSE;

	return MEXIF_TRUE;
}

mexif_uint32 MEXIF_API mexif_jpeg_getmarker( mexif_jpeg_t* pjpeg )
{
	if ( mexif_jpeg_isvalid( pjpeg ) )
	{
		return pjpeg->chunk_type;
	}
	else
		return 0;
}

mexif_pcstr		MEXIF_API mexif_jpeg_getmarkername( int marker )
{
	switch( marker )
	{
	case MEXIF_JPEG_MARKER_SOF0             : return MEXIF_UNICODE("SOF0");
	case MEXIF_JPEG_MARKER_SOF1             : return MEXIF_UNICODE("SOF1");
	case MEXIF_JPEG_MARKER_SOF2             : return MEXIF_UNICODE("SOF2");
	case MEXIF_JPEG_MARKER_SOF3             : return MEXIF_UNICODE("SOF3");
	case MEXIF_JPEG_MARKER_SOF5             : return MEXIF_UNICODE("SOF5");
	case MEXIF_JPEG_MARKER_SOF6             : return MEXIF_UNICODE("SOF6");
	case MEXIF_JPEG_MARKER_SOF7             : return MEXIF_UNICODE("SOF7");
	case MEXIF_JPEG_MARKER_JPG              : return MEXIF_UNICODE("JPG");
	case MEXIF_JPEG_MARKER_SOF9             : return MEXIF_UNICODE("SOF9");
	case MEXIF_JPEG_MARKER_SOF10            : return MEXIF_UNICODE("SOF10");
	case MEXIF_JPEG_MARKER_SOF11            : return MEXIF_UNICODE("SOF11");
	case MEXIF_JPEG_MARKER_SOF13            : return MEXIF_UNICODE("SOF13");
	case MEXIF_JPEG_MARKER_SOF14            : return MEXIF_UNICODE("SOF14");
	case MEXIF_JPEG_MARKER_SOF15            : return MEXIF_UNICODE("SOF15");
	case MEXIF_JPEG_MARKER_DHT				: return MEXIF_UNICODE("DHT");
	case MEXIF_JPEG_MARKER_DAC				: return MEXIF_UNICODE("DAC");
	case MEXIF_JPEG_MARKER_RST0				: return MEXIF_UNICODE("RST0");
	case MEXIF_JPEG_MARKER_RST1				: return MEXIF_UNICODE("RST1");
	case MEXIF_JPEG_MARKER_RST2				: return MEXIF_UNICODE("RST2");
	case MEXIF_JPEG_MARKER_RST3				: return MEXIF_UNICODE("RST3");
	case MEXIF_JPEG_MARKER_RST4				: return MEXIF_UNICODE("RST4");
	case MEXIF_JPEG_MARKER_RST5				: return MEXIF_UNICODE("RST5");
	case MEXIF_JPEG_MARKER_RST6				: return MEXIF_UNICODE("RST6");
	case MEXIF_JPEG_MARKER_RST7				: return MEXIF_UNICODE("RST7");
	case MEXIF_JPEG_MARKER_StartOfImage		: return MEXIF_UNICODE("StartOfImage");
	case MEXIF_JPEG_MARKER_EndOfImage       : return MEXIF_UNICODE("EndOfImage"); 
	case MEXIF_JPEG_MARKER_StartOfScan      : return MEXIF_UNICODE("StartOfScan"); 
	case MEXIF_JPEG_MARKER_DQT				: return MEXIF_UNICODE("DQT");
	case MEXIF_JPEG_MARKER_DNL				: return MEXIF_UNICODE("DNL");
	case MEXIF_JPEG_MARKER_DRI				: return MEXIF_UNICODE("DRI");
	case MEXIF_JPEG_MARKER_DHP				: return MEXIF_UNICODE("DHP");
	case MEXIF_JPEG_MARKER_EXP				: return MEXIF_UNICODE("EXP");
	case MEXIF_JPEG_MARKER_APP0				: return MEXIF_UNICODE("APP0");
	case MEXIF_JPEG_MARKER_APP1				: return MEXIF_UNICODE("APP1");
	case MEXIF_JPEG_MARKER_APP2				: return MEXIF_UNICODE("APP2");
	case MEXIF_JPEG_MARKER_Comment			: return MEXIF_UNICODE("Comment");
	case MEXIF_JPEG_MARKER_PseudoImage		:
		break;
	}

	return 0;
}

mexif_uint32 MEXIF_API mexif_jpeg_getlength( mexif_jpeg_t* pjpeg )
{
	if ( mexif_jpeg_isvalid( pjpeg ) )
	{
		return pjpeg->chunk_length;
	}
	else
		return 0;
}

const void* MEXIF_API mexif_jpeg_getchunk( mexif_jpeg_t* pjpeg )
{
	if ( mexif_jpeg_isvalid( pjpeg ) )
	{
		if ( pjpeg->in_image )
			return pjpeg->header_position;
		else
			return pjpeg->header_position + 2;
	}
	else
		return 0;
}

mexif_uint32 MEXIF_API mexif_jpeg_getoffset( mexif_jpeg_t* pjpeg )
{
	//mexif_uint32 pos;
	if ( pjpeg->data == 0 )
		return 0;

	return pjpeg->header_position - pjpeg->data;
}


mexif_bool MEXIF_API mexif_jpeg_is_exif( mexif_jpeg_t* pjpeg )
{
	if ( mexif_jpeg_isvalid( pjpeg ) )
	{
		if ( pjpeg->chunk_type == MEXIF_JPEG_MARKER_APP1 )
		{
			const unsigned char* p = (const unsigned char*) mexif_jpeg_getchunk( pjpeg );
			size_t      s = mexif_jpeg_getlength( pjpeg );

			p += 2;
			if ( s <= 2 )   s = 0;
			else			s -= 2;

			if ( mexif_is_exif_marker_prefixed( p, s ) )
				return MEXIF_TRUE;
		}
	}
	return MEXIF_FALSE;
}

mexif_bool MEXIF_API mexif_jpeg_is_jfif( mexif_jpeg_t* pjpeg )
{
    if ( mexif_jpeg_isvalid( pjpeg ))
    {
        if ( pjpeg->chunk_type == MEXIF_JPEG_MARKER_APP0 )
        {
			const unsigned char* p = (const unsigned char*) mexif_jpeg_getchunk( pjpeg );
			size_t      s = mexif_jpeg_getlength( pjpeg );

			p += 2;
			if ( s <= 2 )   s = 0;
			else			s -= 2;

			if ( mexif_is_jfif_marker_prefixed( p, s ) )
				return MEXIF_TRUE;
        }
    }

    return MEXIF_FALSE;
}

mexif_return_t	MEXIF_API mexif_jpeg_get_jfif( mexif_jpeg_t* pjpeg, mexif_jfif_partial_t* pjfif )
{
    mexif_return_t ret = mexif_jpeg_fill_jfif( 
        pjfif, mexif_jpeg_getchunk( pjpeg ), mexif_jpeg_getlength( pjpeg ) );
    return ret;
}

mexif_return_t	MEXIF_API mexif_jpeg_fill_jfif( mexif_jfif_partial_t* pjfif, const unsigned char* data, mexif_uint32 size )
{
	//	data must start with "JFIF"
	if ( size >= sizeof(mexif_jfif_partial_t) )
	{
		if ( memcmp( data, "JFIF\0", 5 ) != 0 )
			return MEXIF_RET_CORRUPT_DATA;

		memcpy( pjfif->Identifier, data, 5 );
		data += 5;

		pjfif->VersionMajor = *data++;
		pjfif->VersionMinor = *data++;

		pjfif->Units = *data++;

		pjfif->Xdensity = _GetU16( data, 0, MEXIF_BIG_ENDIAN );
		pjfif->Ydensity = _GetU16( data, 2, MEXIF_BIG_ENDIAN );

		data += 4;

		pjfif->Xthumbnail = *data++;
		pjfif->Ythumbnail = *data++;
		return MEXIF_RET_OK;
	}
	return MEXIF_RET_ERROR;
}

mexif_return_t  MEXIF_API mexif_jpeg_get_sof( mexif_jpeg_t* pjpeg, mexif_frame_partial_t * pframe )
{
    const unsigned char* data = mexif_jpeg_getchunk( pjpeg );
    mexif_uint32 size =         mexif_jpeg_getlength( pjpeg );

    if ( size >= sizeof(mexif_frame_partial_t))
    {
        pframe->Length = size;
        data += 2;
        pframe->PrecisionBit    = *data++;

        pframe->Y   = _GetU16( data, 0, MEXIF_BIG_ENDIAN );
        pframe->X   = _GetU16( data, 2, MEXIF_BIG_ENDIAN );
        data += 4;

        pframe->Nf  = *data++;
        return MEXIF_RET_OK;
    }
    return MEXIF_RET_ERROR;
}


